#include <stdio.h>
#include <error.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

//简单TCP网络程序，可以实现简单聊天室

//******************服务器端********************
//TCP协议是建立连接的可靠传输协议
//1.绑定Ip 和 端口号
//2.被动打开，使可以被建立连接
//3.接受客户端的连接请求
//开始进行事件循环
//  4.接收来自客户端的请求
//  5.开始计算处理客户端发来的请求
//   a)因为这里是回显服务，没有过多的计算处理,只做以下处理
//   b)将客户端的请求显示在标准输出上
//   c)再将请求发回给客户端
//  6.对客户端发来的请求进行响应


//用多线程来实现一个服务器可以同时处理多个用户的请求和响应
//需要注意以下几个小点
//1.线程入口函数中的参数应该是堆上开辟的，不能是栈上或者全局的
//2.主线中应该处理对其他线程的资源的回收
    //这里不能使用线程等待函数,因为线程等待函数是阻塞式等待
    //这样就不能达到同时处理多个用户的请求和响应
    //应该采用线程分离，使内核自动回收线程资源
//3.应该处理文件描述符的关闭问题
    //因为同组线程之间是共用一块虚拟地址空间，共享文件描述符表
    //所以只要在一个线程任务处理完之后关闭文件描述符即可


void * Entry(void * arg)
{

  int listen_socket=*((int *)arg);

  char buf[1024]={0};
  sockaddr_in  peer;
//用来处理一次连接。需要循环的处理

  while(1)
  {
    //7.接收来自客户端的请求
    ssize_t read_size=read(listen_socket,buf,sizeof(buf)-1);

    if(read_size<0)
    {
      continue;
    }
    if(read_size==0)
    {
      printf("client discount!\n");
      //用来标识用户已经关闭连接
      close(listen_socket);
      return NULL;
    }

    buf[read_size]='\0';

    //8.将客户端的请求显示再标准输出上 
    printf("peer %s:%d  say %s",inet_ntoa(peer.sin_addr),ntohs(peer.sin_port),buf);

    //9.将请求发送给客户端

    write(listen_socket,buf,read_size);
  }

  return NULL;
}


void ProcessConnect(int listen_socket)
{

  pthread_t pthread;

  //线程入口函数中参数应该是堆上开辟的
  int *fd=(int *)malloc(sizeof(listen_socket));
  *fd=listen_socket;

  pthread_create(&pthread,0,Entry,(void *)fd);

  //回收线程的资源
  pthread_detach(pthread);
  return;

}


// ./service [IP] [Port]
int main(int argc,char * argv[])
{
  //1.检查命令行参数
  if(argc!=3)
  {
    printf("Usage: ./service [IP] [Port]\n");
    return 1;
  }
  
  //2.创建socket 
  int fd=socket(AF_INET,SOCK_STREAM,0);
  if(fd<0)
  {
    perror("socket");
    return 1;
  }
  
  //3.将要绑定的IP 和 Prot 存储在 sockaddr_in 结构体中
  
  sockaddr_in addr;
  addr.sin_family=AF_INET;
  socklen_t addr_len=sizeof(addr);
  //将点分十进制的IP地址 转化 为32位数字
  addr.sin_addr.s_addr=inet_addr(argv[1]);
  //将端口号转位网络字节序
  addr.sin_port=htons(atoi(argv[2]));


  //4.服务器建立连接
  int bind_ret=bind(fd,(sockaddr *)&addr,addr_len);
  if(bind_ret<0)
  {
    perror("bind");
    return 1;
  }

  //5.服务器开始被动打开，处于可以被建立连接状态
  int listen_ret=listen(fd,5);
  if(listen_ret<0)
  {
    perror("listen");
    return 1;
  }

  //开始事件循环
  

  while(1)
  {
    //6.接受客户端的连接请求
    
    //fd 这个socket用来监听
    int listen_socket=accept(fd,(sockaddr *)&addr,&addr_len);
    //这里的返回值是一个新的 socket 将内核态建立的连接放到用户态执行
    //后续信息传输建立在这个socket
    if(listen_socket<0)
    {
      perror("accept");
      continue;
    }
    //7.处理一次连接
    ProcessConnect(listen_socket);
  }
  close(fd);
  return 0;
}
